]> git.saurik.com Git - apple/security.git/blob - keychain/Signin Metrics/SFSignInAnalytics.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / keychain / Signin Metrics / SFSignInAnalytics.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if __OBJC2__
25
26 #import "SFSignInAnalytics.h"
27 #import "SFSignInAnalytics+Internal.h"
28
29 #import <Analytics/SFAnalytics+Signin.h>
30 #import "SFAnalyticsDefines.h"
31 #import "SFAnalyticsSQLiteStore.h"
32 #import "SFAnalytics.h"
33
34 #import <os/log_private.h>
35 #import <mach/mach_time.h>
36 #import <utilities/SecFileLocations.h>
37 #import <utilities/debugging.h>
38 #import <utilities/SecCFWrappers.h>
39
40 //metrics database location
41 NSString* signinMetricsDatabase = @"signin_metrics";
42
43 //defaults write results location
44 static NSString* const SFSignInAnalyticsDumpLoggedResultsToLocation = @"/tmp/signin_results.txt";
45 static NSString* const SFSignInAnalyticsPersistedEventList = @"/tmp/signin_eventlist";
46
47 //analytics constants
48 static NSString* const SFSignInAnalyticsAttributeRecoverableError = @"recoverableError";
49 static NSString* const SFSignInAnalyticsAttributeErrorDomain = @"errorDomain";
50 static NSString* const SFSignInAnalyticsAttributeErrorCode = @"errorCode";
51 static NSString* const SFSignInAnalyticsAttributeErrorChain = @"errorChain";
52 static NSString* const SFSignInAnalyticsAttributeParentUUID = @"parentUUID";
53 static NSString* const SFSignInAnalyticsAttributeMyUUID = @"myUUID";
54 static NSString* const SFSignInAnalyticsAttributeSignInUUID = @"signinUUID";
55 static NSString* const SFSignInAnalyticsAttributeEventName = @"eventName";
56 static NSString* const SFSignInAnalyticsAttributeSubsystemName = @"subsystemName";
57 static NSString* const SFSignInAnalyticsAttributeBuiltDependencyChains = @"dependencyChains";
58
59 @implementation SFSIALoggerObject
60 + (NSString*)databasePath {
61 return [SFSIALoggerObject defaultAnalyticsDatabasePath:signinMetricsDatabase];
62 }
63
64 + (instancetype)logger
65 {
66 return [super logger];
67 }
68 @end
69
70
71 @interface SFSignInAnalytics ()
72 @property (nonatomic, copy) NSString *signin_uuid;
73 @property (nonatomic, copy) NSString *my_uuid;
74 @property (nonatomic, copy) NSString *parent_uuid;
75 @property (nonatomic, copy) NSString *category;
76 @property (nonatomic, copy) NSString *eventName;
77 @property (nonatomic, copy) NSString *persistencePath;
78
79 @property (nonatomic, strong) NSURL *persistedEventPlist;
80 @property (nonatomic, strong) NSMutableDictionary *eventDependencyList;
81 @property (nonatomic, strong) NSMutableArray *builtDependencyChains;
82
83 @property (nonatomic) BOOL canceled;
84 @property (nonatomic) BOOL stopped;
85
86 @property (nonatomic, strong) os_log_t logObject;
87
88 @property (nonatomic, strong) NSNumber *measurement;
89
90 @property (nonatomic, strong) dispatch_queue_t queue;
91
92 @property (nonatomic, strong) SFSignInAnalytics *root;
93 @property (nonatomic, strong) SFAnalyticsActivityTracker *tracker;
94
95 -(os_log_t) newLogForCategoryName:(NSString*) category;
96 -(os_log_t) logForCategoryName:(NSString*) category;
97
98 @end
99
100 static NSMutableDictionary *logObjects;
101 static const NSString* signInLogSpace = @"com.apple.security.wiiss";
102
103 @implementation SFSignInAnalytics
104
105 + (BOOL)supportsSecureCoding {
106 return YES;
107 }
108
109 -(os_log_t) logForCategoryName:(NSString*) category
110 {
111 return logObjects[category];
112 }
113
114 -(os_log_t) newLogForCategoryName:(NSString*) category
115 {
116 return os_log_create([signInLogSpace UTF8String], [category UTF8String]);
117 }
118
119 - (BOOL)writeDependencyList:(NSError**)error
120 {
121 NSError *localError = nil;
122 if (![NSPropertyListSerialization propertyList: self.root.eventDependencyList isValidForFormat: NSPropertyListXMLFormat_v1_0]){
123 os_log_error(self.logObject, "can't save PersistentState as XML");
124 return false;
125 }
126
127 NSData *data = [NSPropertyListSerialization dataWithPropertyList: self.root.eventDependencyList
128 format: NSPropertyListXMLFormat_v1_0 options: 0 error: &localError];
129 if (data == nil){
130 os_log_error(self.logObject, "error serializing PersistentState to xml: %@", localError);
131 return false;
132 }
133
134 BOOL writeStatus = [data writeToURL:self.root.persistedEventPlist options: NSDataWritingAtomic error: &localError];
135 if (!writeStatus){
136 os_log_error(self.logObject, "error writing PersistentState to file: %@", localError);
137 }
138 if(localError && error){
139 *error = localError;
140 }
141
142 return writeStatus;
143 }
144
145 - (instancetype)initWithSignInUUID:(NSString *)uuid category:(NSString *)category eventName:(NSString*)eventName
146 {
147 self = [super init];
148 if (self) {
149 _signin_uuid = uuid;
150
151 _my_uuid = uuid;
152 _parent_uuid = uuid;
153 _eventName = eventName;
154 _category = category;
155 _root = self;
156 _canceled = NO;
157 _stopped = NO;
158 _builtDependencyChains = [NSMutableArray array];
159
160 //make plist file containing uuid parent/child
161 _persistencePath = [NSString stringWithFormat:@"%@-%@.plist", SFSignInAnalyticsPersistedEventList, eventName];
162 _persistedEventPlist = [NSURL fileURLWithPath:_persistencePath isDirectory:NO];
163
164 _eventDependencyList = [NSMutableDictionary dictionary];
165 [_eventDependencyList setObject:[NSMutableArray array] forKey:_signin_uuid];
166
167 _tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
168 [_tracker start];
169
170 NSError* error = nil;
171 if(![self writeDependencyList:&error]){
172 os_log(self.logObject, "attempting to write dependency list: %@", error);
173 }
174
175 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
176
177 static dispatch_once_t onceToken;
178 dispatch_once(&onceToken, ^{
179 logObjects = [NSMutableDictionary dictionary];
180 });
181 @synchronized(logObjects){
182 if(category){
183 _logObject = [self logForCategoryName:category];
184
185 if(!_logObject){
186 _logObject = [self newLogForCategoryName:category];
187 [logObjects setObject:_logObject forKey:category];
188 }
189 }
190 }
191 }
192 return self;
193 }
194
195 -(instancetype) initChildWithSignInUUID:(NSString*)uuid andCategory:(NSString*)category andEventName:(NSString*)eventName
196 {
197 self = [super init];
198 if (self) {
199 _signin_uuid = uuid;
200
201 _my_uuid = uuid;
202 _parent_uuid = uuid;
203 _eventName = eventName;
204 _category = category;
205 _canceled = NO;
206 }
207 return self;
208 }
209
210 - (void)encodeWithCoder:(NSCoder *)coder {
211 [coder encodeObject:_signin_uuid forKey:@"UUID"];
212 [coder encodeObject:_category forKey:@"category"];
213 [coder encodeObject:_parent_uuid forKey:@"parentUUID"];
214 [coder encodeObject:_my_uuid forKey:@"myUUID"];
215 [coder encodeObject:_measurement forKey:@"measurement"];
216 [coder encodeObject:_eventName forKey:@"eventName"];
217 }
218
219 - (nullable instancetype)initWithCoder:(NSCoder *)decoder
220 {
221 self = [super init];
222 if (self) {
223 _signin_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"UUID"];
224 _category = [decoder decodeObjectOfClass:[NSString class] forKey:@"category"];
225 _parent_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"parentUUID"];
226 _my_uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"myUUID"];
227 _measurement = [decoder decodeObjectOfClass:[NSString class] forKey:@"measurement"];
228 _eventName = [decoder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
229 _queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
230
231 if(_signin_uuid == nil ||
232 _category == nil ||
233 _parent_uuid == nil){
234 [decoder failWithError:[NSError errorWithDomain:@"securityd" code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Failed to decode SignInAnalytics object"}]];
235 return nil;
236 }
237 }
238 return self;
239 }
240
241 - (SFSignInAnalytics*)newSubTaskForEvent:(NSString*)eventName
242 {
243 SFSignInAnalytics *newSubTask = [[SFSignInAnalytics alloc] initChildWithSignInUUID:self.signin_uuid andCategory:self.category andEventName:self.eventName];
244 if(newSubTask){
245 newSubTask.my_uuid = [NSUUID UUID].UUIDString;
246 newSubTask.parent_uuid = self.my_uuid;
247 newSubTask.signin_uuid = self.signin_uuid;
248
249 newSubTask.category = self.category;
250 newSubTask.eventName = [eventName copy];
251 newSubTask.root = self.root;
252 newSubTask.canceled = NO;
253 newSubTask.stopped = NO;
254
255 newSubTask.queue = dispatch_queue_create("com.apple.security.SignInAnalytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
256 newSubTask.tracker = [[SFSIALoggerObject logger] logSystemMetricsForActivityNamed:eventName withAction:nil];
257 [newSubTask.tracker start];
258
259 @synchronized(_eventDependencyList){
260 NSMutableArray *parentEntry = [newSubTask.root.eventDependencyList objectForKey:newSubTask.parent_uuid];
261
262 //add new subtask entry to parent event's list
263 [parentEntry addObject:newSubTask.my_uuid];
264 [newSubTask.root.eventDependencyList setObject:parentEntry forKey:newSubTask.parent_uuid];
265
266 //create new array list for this new subtask incase it has subtasks
267 [newSubTask.root.eventDependencyList setObject:[NSMutableArray array] forKey:newSubTask.my_uuid];
268 NSError* error = nil;
269 if(![newSubTask writeDependencyList:&error]){
270 os_log(self.logObject, "attempting to write dependency list: %@", error);
271 }
272 }
273 }
274
275 return newSubTask;
276 }
277
278 - (void)logRecoverableError:(NSError*)error
279 {
280
281 if (error == nil){
282 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
283 return;
284 }
285
286 os_log_error(self.logObject, "%@", error);
287
288 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
289
290 [eventAttributes setValuesForKeysWithDictionary:@{
291 SFSignInAnalyticsAttributeRecoverableError : @(YES),
292 SFSignInAnalyticsAttributeErrorDomain : error.domain,
293 SFSignInAnalyticsAttributeErrorCode : @(error.code),
294 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
295 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
296 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
297 SFSignInAnalyticsAttributeEventName : self.eventName,
298 SFSignInAnalyticsAttributeSubsystemName : self.category
299 }];
300
301 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:eventAttributes];
302
303 }
304
305 - (void)logUnrecoverableError:(NSError*)error
306 {
307 if (error == nil){
308 os_log_error(self.logObject, "attempting to log a nil error for event:%@", self.eventName);
309 return;
310 }
311
312 os_log_error(self.logObject, "%@", error);
313
314 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
315
316 [eventAttributes setValuesForKeysWithDictionary:@{
317 SFSignInAnalyticsAttributeRecoverableError : @(NO),
318 SFSignInAnalyticsAttributeErrorDomain : error.domain,
319 SFSignInAnalyticsAttributeErrorCode : @(error.code),
320 SFSignInAnalyticsAttributeMyUUID : self.my_uuid,
321 SFSignInAnalyticsAttributeParentUUID : self.parent_uuid,
322 SFSignInAnalyticsAttributeSignInUUID : self.signin_uuid,
323 SFSignInAnalyticsAttributeEventName : self.eventName,
324 SFSignInAnalyticsAttributeSubsystemName : self.category
325 }];
326
327 [[SFSIALoggerObject logger] logHardFailureForEventNamed:self.eventName withAttributes:eventAttributes];
328 }
329
330 -(void)cancel
331 {
332 dispatch_sync(self.queue, ^{
333 [self.tracker cancel];
334 self.canceled = YES;
335 os_log(self.logObject, "canceled timer for %@", self.eventName);
336 });
337 }
338
339 - (void)stopWithAttributes:(NSDictionary<NSString*, id>*)attributes
340 {
341 dispatch_sync(self.queue, ^{
342
343 if(self.canceled || self.stopped){
344 return;
345 }
346
347 self.stopped = YES;
348
349 [self.tracker stop];
350
351 NSMutableDictionary *mutableAttributes = nil;
352
353 if(attributes){
354 mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
355 }
356 else{
357 mutableAttributes = [NSMutableDictionary dictionary];
358 }
359 mutableAttributes[SFSignInAnalyticsAttributeMyUUID] = self.my_uuid;
360 mutableAttributes[SFSignInAnalyticsAttributeParentUUID] = self.parent_uuid;
361 mutableAttributes[SFSignInAnalyticsAttributeSignInUUID] = self.signin_uuid;
362 mutableAttributes[SFSignInAnalyticsAttributeEventName] = self.eventName;
363 mutableAttributes[SFSignInAnalyticsAttributeSubsystemName] = self.category;
364
365 [mutableAttributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id obj, BOOL * stop) {
366 os_log(self.logObject, "event: %@, %@ : %@", self.eventName, key, obj);
367 }];
368
369 [[SFSIALoggerObject logger] logSuccessForEventNamed:self.eventName];
370 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:self.eventName withAttributes:mutableAttributes];
371 });
372 }
373
374 -(BOOL) writeResultsToTmp {
375
376 bool shouldWriteResultsToTemp = NO;
377 CFBooleanRef toTmp = (CFBooleanRef)CFPreferencesCopyValue(CFSTR("DumpResultsToTemp"),
378 CFSTR("com.apple.security"),
379 kCFPreferencesAnyUser, kCFPreferencesAnyHost);
380 if(toTmp && CFGetTypeID(toTmp) == CFBooleanGetTypeID()){
381 if(toTmp == kCFBooleanFalse){
382 os_log(self.logObject, "writing results to splunk");
383 shouldWriteResultsToTemp = NO;
384 }
385 if(toTmp == kCFBooleanTrue){
386 os_log(self.logObject, "writing results to /tmp");
387 shouldWriteResultsToTemp = YES;
388 }
389 }
390
391 CFReleaseNull(toTmp);
392 return shouldWriteResultsToTemp;
393 }
394
395 - (void)processEventChainForUUID:(NSString*)uuid dependencyChain:(NSString*)dependencyChain
396 {
397 NSString* newChain = dependencyChain;
398
399 NSArray* children = [self.root.eventDependencyList objectForKey:uuid];
400 for (NSString* child in children) {
401 newChain = [NSString stringWithFormat:@"%@, %@", dependencyChain, child];
402 [self processEventChainForUUID:child dependencyChain:newChain];
403 }
404 if([children count] == 0){
405 [self.root.builtDependencyChains addObject:newChain];
406 os_log(self.logObject, "current dependency chain list: %@", newChain);
407 }
408 }
409
410 - (void)signInCompleted
411 {
412 //print final
413 os_log(self.logObject, "sign in complete");
414 NSError* error = nil;
415
416 //create dependency chains and log them
417 [self processEventChainForUUID:self.root.my_uuid dependencyChain:self.root.signin_uuid];
418 //write to database
419 if([self.root.builtDependencyChains count] > 0){
420 NSDictionary* eventAttributes = @{SFSignInAnalyticsAttributeBuiltDependencyChains : self.root.builtDependencyChains};
421 [[SFSIALoggerObject logger] logSoftFailureForEventNamed:SFSignInAnalyticsAttributeBuiltDependencyChains withAttributes:eventAttributes];
422 }
423
424 BOOL writingToTmp = [self writeResultsToTmp];
425 if(writingToTmp){ //writing sign in analytics to /tmp
426 os_log(self.logObject, "logging to /tmp");
427
428 NSData* eventData = [NSKeyedArchiver archivedDataWithRootObject:[[SFSIALoggerObject logger].database allEvents] requiringSecureCoding:YES error:&error];
429 if(eventData){
430 [eventData writeToFile:SFSignInAnalyticsDumpLoggedResultsToLocation options:0 error:&error];
431
432 if(error){
433 os_log_error(self.logObject, "error writing to file [%@], error:%@", SFSignInAnalyticsDumpLoggedResultsToLocation, error);
434 }else{
435 os_log(self.logObject, "successfully wrote sign in analytics to:%@", SFSignInAnalyticsDumpLoggedResultsToLocation);
436 }
437 }else{
438 os_log_error(self.logObject, "collected no data");
439 }
440 }else{ //writing to splunk
441 os_log(self.logObject, "logging to splunk");
442 }
443
444 //remove dependency list
445 BOOL removedPersistedDependencyList = [[NSFileManager defaultManager] removeItemAtPath:self.persistencePath error:&error];
446
447 if(!removedPersistedDependencyList || error){
448 os_log(self.logObject, "encountered error when attempting to remove persisted event list: %@", error);
449 }
450
451 }
452
453 @end
454 #endif
455